#include "kbc.h"

/** Initializes the KeyBoard Controller. If 'debug' is not 0 every
 * read or write to the KBC, together with the read or writed data is
 * echoed to the screen. In this mode the function name being executed
 * is printed followed by a ':', add=xx means the I/O address written,
 * and data=xx the data read or written.
 *
 *  A 1 is returned if a mouse is found.
 *
 * The function starts to flush any pending data in the KBC, reading
 * the data register until a timeout is received.
 *
 * Then it enables the KBC mouse interface, send a MouseReset to the
 * mouse and checks for the correct answer (0xFA, 0xAA then 0x00).  If
 * the mouse was detected, data streaming is disabled (StreamDisable),
 * SampleRate is set to 100, Resolution is set to 8 counts/mm, Scaling
 * is set to 1:1, the mouse interrupts generation are enabled at the
 * KBC (not at the PIC!), and data streaming enabled
 * (StreamEnable). All this (but StreamEnable) shouldn't be necessary,
 * as MouseReset is supposed to do it properly.
 *
 * The KBDRate is also set to its maximum speed.
 *
 * Uses write_kbc_cmd(), write_kbc_data(), read_kbc() and write_aux()
 *
 * Example Output generated with debug = 1:
 * <code><pre>
read_kbc: timeout
write_kbc: add=64 data=d4
write_kbc: add=60 data=ff
read_kbc: data=fa
read_kbc: data=aa
read_kbc: data= 0
write_kbc: add=60 data=f5
read_kbc: data=fa
write_kbc: add=64 data=a7
write_kbc: add=64 data=d4
write_kbc: add=60 data=f5
read_kbc: data=fa
write_kbc: add=60 data=f3
read_kbc: data=fa
write_kbc: add=60 data= 0
read_kbc: data=fa
write_kbc: add=64 data=d4
write_kbc: add=60 data=f3
read_kbc: data=fa
write_kbc: add=64 data=d4
write_kbc: add=60 data= a
read_kbc: data=fa
write_kbc: add=64 data=d4
write_kbc: add=60 data=e8
read_kbc: data=fa
write_kbc: add=64 data=d4
write_kbc: add=60 data= 1
read_kbc: data=fa
write_kbc: add=64 data=20
read_kbc: data=45
write_kbc: add=64 data=60
write_kbc: add=60 data=47
read_kbc: timeout
write_kbc: add=60 data=f4
read_kbc: data=fa
write_kbc: add=64 data=a8
write_kbc: add=64 data=d4
write_kbc: add=60 data=f4
read_kbc: data=fa
 * </pre></code>
 */
int kbc_init(int debug);

/** Disable the mouse, sending a StreamDisable to the mouse, then
 * disables generation of mouse interrupts at the KBC, sends a
 * DisableMouse to the KBC and finnaly disable mouse interrupts at the
 * PIC2
 */
void mouse_disable(void) {
	write_kbc_cmd(StreamDisable);
	// falta aqui qualquer coisa? "disables generation of mouse interrupts at the KBC
	write_kbc_cmd(DisableMouse);
	disable_irq(MOUSE_IRQ);
}

/** Write 'data' to the command register, returning 0 for OK or -1 on
 * timeout.
 * 
 * Uses write_kbc()
 */
int  write_kbc_cmd( unsigned data) {
	return write_kbc(CMD_REG, data);
}

/** Returns 1 if a DOS mouse driver is installed, 0 if not.
 *  
 * Needed to know if a DOS mouse driver is installed in order
 *  to reinstall it at program end.
 * 
 */ 
int has_DOS_mouse_ISR() {
	write_kbc_cmd(TestKBD);
	int estado = read_kbc();
	if (estado == 0) return 1;
	return 0;
}
  
/** Write 'data' to the data register, returning ACK, Error or -1 on timeout.
 *
 * If a Resend reply is received from the KBC, it retries up to 3 times to
 * write the data.
 *
 * Uses write_kbc() and read_kbc()
 */
int  write_kbc_data( unsigned data) {
	int tentativas = 0;
	while ( tentativas < 3 ) {
		if ( write_kbc(DATA_REG, data) == -1 ) {
			// Erro
			return -1;
		}
		
		if ( read_kbc() == Resend ) {
			tentativas++;
		} else {
			return ACK;
		}
		
	}
	return Error;
}

/** Send 'cmd' to the mouse, prefacing it with the WriteMouse command
 * to the command register.
 *
 * If a Resend reply is received from the KBC, it retries up to 3 times to
 * write 'cmd', resending the whole sequence.
 *
 * Uses write_kbc_cmd(), write_kbc() and read_kbc()
 */ 
int  write_aux(unsigned cmd) {
	int tentativas = 0;
	while ( tentativas < 3 ) {
		if ( write_kbc_cmd(WriteMouse) == -1 ) {
			// Erro
			return -1;
		}
		if ( write_kbc_cmd(cmd) == -1 ) {
			// Erro
			return -1;
		}
		
		if ( read_kbc() == Resend ) {
			tentativas++;
		} else {
			return ACK;
		}
		
	}
	return Error;
}

/** Returns the data read a the data register.
*
*  Waits for OBF on status register up to KBC_TIMEOUT milliseconds,
*  then reads and returns the data read at the data register. Returns
*  -1 on timeout or transmission error.
*/
int read_kbc(void) {
	int estado, tempo = 0;
	while( tempo < KBC_TIMEOUT ) {
		// Le estado
		estado = inportb(STAT_REG);
		if ( (estado & OBF) != 0 ) {
			// Há dados para ler
			int dados = inportb(DATA_REG);
			if ( (dados & ( PAR_ERR | TO_ERR)) != 0 ) {
				return -1;
			}
			if ( (dados & AUX) == 0) {
				// Dados do teclado
				return dados;
			} else {
				// Dados do rato
				return dados;
			}
			return 0;
		}
		tempo++;
		mili_sleep(100);
	}
	return -1;
}

/** Write 'data' at IO address 'adr' , returning -1 on error or timeout.
 *
 * Waits for IBF on status register , than writes data to 'adr',
 * return -1 on timeout.
 */
int  write_kbc(unsigned adr, unsigned data) {
	int estado, tempo = 0;
	while( tempo < KBC_TIMEOUT ) {
		// Le estado
		estado = inportb(STAT_REG);
		if ( (estado & IBF) == 0 ) {
			// Pode-se escrever
			outportb(adr, data);
			return 1;
		}
		tempo++;
		mili_sleep(100);
	}
	return -1;
}

/** shortky blink all three keyboard LEDs
 *
 */
void blink_leds(void) {
	write_kbc_data(WriteLEDS);
	write_kbc_data(SCROLL_LOCK | NUM_LOCK | CAPS_LOCK);
	mili_sleep(1000);
	write_kbc_data(WriteLEDS);
	write_kbc_data(0x0);
}

/** shortky blink left(1), middle(2) or right(3) keyboard LED
 *
 */
void blink_led(uchar led) {
	write_kbc_data(WriteLEDS);
	write_kbc_data(led);
	mili_sleep(500);
	write_kbc_data(WriteLEDS);
	write_kbc_data(0x0);
}

void initKeyBoard( void (*t0_handler)(void) ) {
	install_c_irq_handler(KBD_IRQ, t0_handler, &keyboard_old_irq, &keyboard_new_irq);
	enable_irq(KBD_IRQ);
}

void endKeyBoard() {
	//disable_irq(KBD_IRQ); // Se for executado, ao sair o teclado nao fica mais disponivel
	reinstall_c_irq_handler(KBD_IRQ, &keyboard_old_irq, &keyboard_new_irq);
}

void handler_keyboard() {
	char key = inportb(DATA_REG);
	queuePut(&queue_keyboard,key);
	outportb(PIC1_CMD, EOI);
}

void initMouse( void (*mouse_handler)(void) ) {
	install_asm_irq_handler(MOUSE_IRQ, mouse_handler, &mouse_old_irq);
}

void endMouse() {
	disable_irq(MOUSE_IRQ);
	reinstall_asm_irq_handler(MOUSE_IRQ, &mouse_old_irq);
}
